package com.boy.rpc.framework.console.service.impl;

import com.boy.rpc.framework.console.Constant;
import com.boy.rpc.framework.console.appenum.RefreshModeEnum;
import com.boy.rpc.framework.console.appenum.ShowTypeEnum;
import com.boy.rpc.framework.console.bean.Pagination;
import com.boy.rpc.framework.console.bean.RKey;
import com.boy.rpc.framework.console.exception.ConcurrentException;
import com.boy.rpc.framework.console.service.ViewService;
import com.boy.rpc.framework.console.utils.InitKeysCacheUtil;
import com.boy.rpc.framework.console.utils.ztree.RedisZtreeUtil;
import com.boy.rpc.framework.console.utils.ztree.ZNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.Semaphore;

@Service
public class ViewServiceImpl implements ViewService {

    private static final Logger logger = LoggerFactory.getLogger(ViewServiceImpl.class);

    @Override
    public void changeRefreshMode(String mode) {
        Constant.refreshMode = RefreshModeEnum.valueOf(mode);
    }

    @Override
    public Set<ZNode> getLeftTree() {
        return getLeftTree(Constant.refreshMode);
    }

    private Set<ZNode> getLeftTree(RefreshModeEnum refreshMode) {
        switch (refreshMode) {
            case manually:
                break;
            case auto:
                for (Map<String, Object> redisServerMap : Constant.redisServerCache) {
                    RedisZtreeUtil.refreshRedisNavigateZtree((String) redisServerMap.get("name"));
                }
                break;
        }
        return new TreeSet<ZNode>(Constant.redisNavigateZtree);

    }

    @Override
    public Set<ZNode> refresh() {
        boolean permit = getUpdatePermition();
        Set<ZNode> zTree = null;
        if (permit) {
            try {
                InitKeysCacheUtil.logCurrentTime("try {");
                for (Map<String, Object> redisServerMap : Constant.redisServerCache) {
                    InitKeysCacheUtil.logCurrentTime("refreshKeys(" + (String) redisServerMap.get("name"));
                    for (int i = 0; i <= Constant.REDIS_DEFAULT_DB_SIZE; i++) {
                        refreshKeys((String) redisServerMap.get("name"), i);
                    }
                    InitKeysCacheUtil.logCurrentTime("refreshServerTree(" + (String) redisServerMap.get("name"));
                    zTree = refreshServerTree((String) redisServerMap.get("name"), Constant.DEFAULT_DBINDEX);
                    InitKeysCacheUtil.logCurrentTime("continue");
                }
                InitKeysCacheUtil.logCurrentTime("finally {");
            } finally {
                finishUpdate();
            }
        } else {
            // test limit flow System.out.println("no permit");
        }
        return zTree;
    }

    @Override
    public void refreshAllKeys() {
        boolean permit = getUpdatePermition();
        try {
            for (Map<String, Object> redisServerMap : Constant.redisServerCache) {
                for (int i = 0; i <= Constant.REDIS_DEFAULT_DB_SIZE; i++) {
                    refreshKeys((String) redisServerMap.get("name"), i);
                }
            }
        } finally {
            finishUpdate();
        }
    }

    private void refreshKeys(String serverName, int dbIndex) {
        RedisTemplate redisTemplate = Constant.redisTemplatesMap.get(serverName);
        InitKeysCacheUtil.initRedisKeysCache(redisTemplate, serverName, dbIndex, Constant.REDIS_PROPERTIES_KEYS_DEFAULT_VALUE);
    }

    private Set<ZNode> refreshServerTree(String serverName,
                                         int dbIndex) {
        RedisZtreeUtil.refreshRedisNavigateZtree(serverName);
        return new TreeSet<ZNode>(Constant.redisNavigateZtree);
    }

    @Override
    public Set<RKey> getRedisKeys(Pagination pagination, String serverName, String dbIndex, String[] keyPrefixs, String queryKey, String queryValue) {
        List<RKey> allRedisKeys = Constant.redisKeysListMap.get(serverName + Constant.DEFAULT_SEPARATOR + dbIndex);

        Set<RKey> resultRedisKeys = null;

        if (allRedisKeys == null || allRedisKeys.size() == 0) {
            pagination.setMaxentries(0);
            resultRedisKeys = new TreeSet<RKey>();
            return resultRedisKeys;
        }

        if (keyPrefixs == null || keyPrefixs.length == 0) {
            InitKeysCacheUtil.logCurrentTime("keyPrefixs == null");
            if (StringUtils.isEmpty(queryValue)) {
                InitKeysCacheUtil.logCurrentTime("new TreeSet<RKey>(allRedisKeys);");
                int toIndex = pagination.getToIndex() > allRedisKeys.size() ? allRedisKeys.size() : pagination.getToIndex();
                List<RKey> resultList = allRedisKeys.subList(pagination.getFromIndex(), toIndex);
                resultRedisKeys = new TreeSet<RKey>(resultList);
                pagination.setMaxentries(allRedisKeys.size());
            } else {
                List<RKey> queryRedisKeys = getQueryRedisKeys(allRedisKeys, queryKey, queryValue);
                Collections.sort(queryRedisKeys);//arraylist sort
                int toIndex = pagination.getToIndex() > queryRedisKeys.size() ? queryRedisKeys.size() : pagination.getToIndex();
                List<RKey> resultList = queryRedisKeys.subList(pagination.getFromIndex(), toIndex);
                resultRedisKeys = new TreeSet<RKey>(resultList);
                pagination.setMaxentries(queryRedisKeys.size());
            }
        } else {
            StringBuffer keyPrefix = new StringBuffer("");
            for (String prefix : keyPrefixs) {
                keyPrefix.append(prefix).append(Constant.DEFAULT_REDISKEY_SEPARATOR);
            }
            List<RKey> conformRedisKeys = getConformRedisKeys(allRedisKeys, keyPrefix.toString());
            Collections.sort(conformRedisKeys);//arraylist sort
            int toIndex = pagination.getToIndex() > conformRedisKeys.size() ? conformRedisKeys.size() : pagination.getToIndex();
            List<RKey> resultList = conformRedisKeys.subList(pagination.getFromIndex(), toIndex);
            resultRedisKeys = new TreeSet<RKey>(resultList);
            pagination.setMaxentries(conformRedisKeys.size());
        }
        return resultRedisKeys;
    }

    private List<RKey> getQueryRedisKeys(List<RKey> allRedisKeys, String queryKey, String queryValue) {
        List<RKey> rKeySet = new ArrayList<RKey>();
        for (RKey rKey : allRedisKeys) {
            switch (queryKey) {
                case Constant.MIDDLE_KEY:
                    if (rKey.contains(queryValue)) {
                        rKeySet.add(rKey);
                    }
                    break;
                case Constant.HEAD_KEY:
                    if (rKey.startsWith(queryValue)) {
                        rKeySet.add(rKey);
                    }
                    break;
                case Constant.TAIL_KEY:
                    if (rKey.endsWith(queryValue)) {
                        rKeySet.add(rKey);
                    }
                    break;
            }
        }
        return rKeySet;
    }

    private List<RKey> getConformRedisKeys(List<RKey> allRedisKeys, String keyPrefix) {
        List<RKey> rKeySet = new ArrayList<RKey>();
        for (RKey rKey : allRedisKeys) {
            if (rKey.startsWith(keyPrefix)) {
                rKeySet.add(rKey);
            }
        }
        return rKeySet;
    }

    @Override
    public void changeShowType(String state) {
        Constant.showType = ShowTypeEnum.valueOf(state);
        switch (Constant.showType) {
            case show:
                //get redisKeys again if init keys with ShowTypeEnum.hide
                refreshAllKeys();
                break;
            case hide:
                break;
        }
    }

    protected volatile Semaphore limitUpdate = new Semaphore(1);
    protected static final int LIMIT_TIME = 3; //unit : second

    protected static ThreadLocal<Semaphore> updatePermition = new ThreadLocal<Semaphore>() {
        @Override
        protected Semaphore initialValue() {
            return null;
        }
    };

    protected static ThreadLocal<Long> startTime = new ThreadLocal<Long>() {
        protected Long initialValue() {
            return 0l;
        }
    };

    private Semaphore getSempahore() {
        startTime.set(System.currentTimeMillis());
        updatePermition.set(limitUpdate);
        return updatePermition.get();

    }

    protected boolean getUpdatePermition() {
        Semaphore sempahore = getSempahore();
        boolean permit = sempahore.tryAcquire(1);
        return permit;
    }

    protected void finishUpdate() {
        Semaphore semaphore = updatePermition.get();
        if (semaphore == null) {
            throw new ConcurrentException("semaphore==null");
        }
        final Semaphore fsemaphore = semaphore;
        new Thread(new Runnable() {

            Semaphore RSemaphore;

            {
                RSemaphore = fsemaphore;
            }

            @Override
            public void run() {
                long start = startTime.get();
                long now = System.currentTimeMillis();
                try {
                    long needWait = start + LIMIT_TIME * 1000 - now;
                    if (needWait > 0L) {
                        Thread.sleep(needWait);
                    }
                } catch (InterruptedException e) {
                    logger.warn("finishUpdate 's release semaphore thread had be interrupted");
                }
                RSemaphore.release(1);
                InitKeysCacheUtil.logCurrentTime("semaphore.release(1) finish");
            }
        }).start();
    }

}
